﻿//#define NO_NET
//#define TEST_API
//#define NO_SDK

using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Google.Protobuf;
using Internal.Runtime.Net;
using Internal.Runtime.Services.NetSimulate;
using KuiHuaBaoDian.Services.Net;
using KuiHuaBaoDian.Services.UGUI;
using Runtime.Net;
using Runtime.Services.NetSimulate;
using UnityEngine;
using UnityEngine.Assertions;
using System.Linq;

#if !TEST_API && UNITY_EDITOR
using Newtonsoft.Json;
using Runtime.Settings;
using UnityEngine.Networking;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build.DataBuilders;
using UnityEditorInternal;
#endif

namespace Runtime.Net {

    public class DFR_Net : IDFR_NetInternal {

        public delegate UniTask<bool> ReconnectDelegate();
        public event ReconnectDelegate Reconnect;

        private static bool m_IS_DEBUG = false;
        public static bool IS_DEBUG {
            get => m_IS_DEBUG;
            set {
                m_IS_DEBUG = value;
                NetService.Instance.LogEnabled = value;
                UIService.Instance.LogEnabled = value;
            }
        }

        private static bool m_IS_DEBUG_AD = false;
        public static bool IS_DEBUG_AD {
            get => m_IS_DEBUG_AD;
            set => m_IS_DEBUG_AD = value;
        }

#if !TEST_API && UNITY_EDITOR
        private static DFR_NetConnectionProjectSettings m_ConnectionProjectSettings;
        public DFR_NetConnectionProjectSettings ConnectionProjectSettings => m_ConnectionProjectSettings;

        private static DFR_NetConnectionPreferenceSettings m_NetConnectionPreferenceSettings;
        public DFR_NetConnectionPreferenceSettings ConnectionPreferenceSettings => m_NetConnectionPreferenceSettings;

        [InitializeOnLoadMethod]
        static void InitializeOnLoad() {
            var args = System.Environment.GetCommandLineArgs();
            // 检查特定参数（例如"-profiler"）
            bool isProfilerProcess = System.Array.Exists(args, arg => arg.Contains("-profiler"));
            if (isProfilerProcess) {
                return;
            }

            if (m_ConnectionProjectSettings == null) {
                var files = InternalEditorUtility.LoadSerializedFileAndForget($"ProjectSettings/DFR/{nameof(DFR_NetConnectionProjectSettings)}.asset");
                if (files != null && files.Length > 0 && files.All(item => item != null)) {
                    m_ConnectionProjectSettings = (DFR_NetConnectionProjectSettings)files[0];
                }
            }
            if (m_NetConnectionPreferenceSettings == null) {
                var files = InternalEditorUtility.LoadSerializedFileAndForget($"UserSettings/DFR/{nameof(DFR_NetConnectionPreferenceSettings)}.asset");
                if (files != null && files.Length > 0 && files.All(item => item != null)) {
                    m_NetConnectionPreferenceSettings = (DFR_NetConnectionPreferenceSettings)files[0];
                }
            }
        }

        private static string s_CDN_URL;
        public static string CDN_URL {
            get {
                if (s_CDN_URL == null) {
                    s_CDN_URL = string.Empty;
                    var settings = AddressableAssetSettingsDefaultObject.Settings;
                    // 调试远程资源
                    if (settings.ActivePlayModeDataBuilder is BuildScriptPackedPlayMode) {
                        try {
                            var NET_REMOTE_SETTINGS_JSON_URL = PlayerSettings.GetTemplateCustomValue("NET_REMOTE_SETTINGS_JSON_URL");
                            var REMOTE_NAME = PlayerSettings.GetTemplateCustomValue("REMOTE_NAME");
                            var webRequest = UnityWebRequest.Get(NET_REMOTE_SETTINGS_JSON_URL);
                            webRequest.SetRequestHeader("If-Modified-Since", "0");
                            var op = webRequest.SendWebRequest();
                            while (!op.isDone) { }
                            if (webRequest.isDone && webRequest.result == UnityWebRequest.Result.Success) {

                                var netRemoteSettings = JsonConvert.DeserializeObject<DFR_NetRemoteSettings>(webRequest.downloadHandler.text);
                                var netRemoteSetting = netRemoteSettings.Get(REMOTE_NAME);
                                s_CDN_URL = netRemoteSetting.CDNURL;
                            }
                        } catch (Exception e) {
                            Debug.LogException(e);
                        }
                    }
                }
                return s_CDN_URL;
            }
        }
#else
        public static string CONNECTION_URL;
        public static string CONNECTION_EXTRA;
        public static string CONNECTION_PLATFORM;
        public static string CDN_URL;
#if NO_SDK
        public static string ENV_TAG;
#endif
#endif

        private struct CallbackInstanceID {

            public int Value { get; private set; }

            public CallbackInstanceID(Type responseType, Delegate callback) {
                Value = HashCode.Combine(responseType, callback);
            }
        }

        private class AddingListenerRecord {
            public INetPacketID MessageID;
            public NetCallbackID CallbackID;
            public Action<INetPacket> Callback;
        }

        void IDFR_NetInternal.Initialize() {
#if NO_NET
            IsEnabled = false;
#elif !TEST_API && UNITY_EDITOR
            if (m_NetConnectionPreferenceSettings != null) {
                IsEnabled = m_NetConnectionPreferenceSettings.IsEnabled;
            } else {
                IsEnabled = true;
            }
#else
            IsEnabled = true;
#endif
            if (IsEnabled) {
                m_Receiver = NetService.Instance.AsReceiver();
                m_Sender = NetService.Instance.AsSender();
                NetService.Instance.ConnectionStatusChanged += ConnectionStatusChanged;
            } else {
                (NetSimulateServiceBase.Instance as IDFR_NetSimulateServiceBaseInternal).Initialize();
                m_Receiver = NetSimulateServiceBase.Instance;
                m_Sender = NetSimulateServiceBase.Instance;
                (this as IDFR_NetInternal).SetSession(new DFR_NetSession(NetConnectionHandle.None));
            }

            while (m_AddingListenerQueue.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                var addingRecord = m_AddingListenerQueue.Dequeue();
                m_Receiver.AddListener(addingRecord.MessageID, addingRecord.CallbackID, addingRecord.Callback);
            }
        }

        private async void ConnectionStatusChanged(NetConnectionHandle handle, INetConnection.Status status) {
            if (status == INetConnection.Status.Error) {
                if (m_Session != null && m_Session.ConnectionHandle == handle) {
                    Disconnect().Forget();
                    while (KHBD.Context.IsApplicationPlaying2) {
                        var task = Reconnect?.Invoke();
                        if (task.HasValue) {
                            if (await task.Value) {
                                break;
                            }
                            await UniTask.Delay(1000, true);
                        } else {
                            break;
                        }
                    }
                }
            }
        }

        private DFR_NetSession m_Session;
        public UniTask<DFR_NetSession> Session => GetSession();
        public async UniTask<DFR_NetSession> GetSession() {
            if (!KHBD.Context.IsApplicationPlaying2) {
                return null;
            }
            await KHBD.Context.WaitUntil(() => HasConnection && m_Session.IsPublic);
            return m_Session;
        }

        void IDFR_NetInternal.OnApplicationQuit() {
            if (HasConnection) {
                NetService.Instance.Close(m_Session.ConnectionHandle);
            }
        }

        public bool HasConnection {
            get => m_Session != null && m_Session.IsConnected;
        }

        public bool IsEnabled { get; private set; }

        private readonly Dictionary<CallbackInstanceID, NetCallbackID> m_CallbackInstanceIDDictionary = new();
        private readonly Dictionary<object, Action<INetPacket>> m_CallbackDictionary = new();
        private readonly Queue<AddingListenerRecord> m_AddingListenerQueue = new();

        private INetReceiver m_Receiver;
        private INetSender m_Sender;

        private CancellationTokenSource m_HeartBeatCTS;

        private float m_HeartBeatTimeInterval;
        void IDFR_NetInternal.SetHeartBeatTimeInterval(float value) {
            m_HeartBeatTimeInterval = value;
        }

        private float m_DefaultResponseTimeout;
        void IDFR_NetInternal.SetDefaultResponseTimeout(float value) {
            m_DefaultResponseTimeout = value;
            m_Session?.AsSetter().SetDefaultResponseTimeout(m_DefaultResponseTimeout);
        }

        void IDFR_NetInternal.SetSession(DFR_NetSession value) {
            Assert.IsNotNull(value);

            m_Session = value;
            m_Session.AsSetter().SetDefaultResponseTimeout(m_DefaultResponseTimeout);
            var @internal = m_Session as IDFR_NetSessionInternal;
            @internal.SetReceiver(m_Receiver);
            @internal.SetSender(m_Sender);
            Daemon().Forget();
        }

        private const int HEART_BEAT_REQ_SAFE_TIME_INTERVAL = 15 * 1000;
        private async UniTaskVoid Daemon() {
            if (!IsEnabled) {
                return;
            }
            var session = m_Session;
            var cts = m_HeartBeatCTS = new CancellationTokenSource();
            var ms = m_HeartBeatTimeInterval * 1000;
            var delay = (int)Mathf.Clamp(ms, HEART_BEAT_REQ_SAFE_TIME_INTERVAL, int.MaxValue);
            while (!cts.IsCancellationRequested && session.IsConnected && KHBD.Context.IsApplicationPlaying2) {
                var message = new KeepAlive();
                await session.Send(message);
                await KHBD.Context.Delay(delay, cancellationToken: cts.Token);
            };
            if (m_HeartBeatCTS == cts) {
                m_HeartBeatCTS = null;
            }
            cts.Dispose();
            if (m_Session == session) {
                m_Session = null;
            }
            if (session != null && session.IsConnected) {
                await NetService.Instance.Close(session.ConnectionHandle);
            }
            session.Dispose();
        }

        public async UniTask Disconnect() {
            var session = m_Session;

            if (m_HeartBeatCTS != null) {
                m_HeartBeatCTS.Cancel();
                m_HeartBeatCTS = null;
            }

            if (m_Session != null) {
                m_Session = null;
            }

            if (session != null && session.IsConnected) {
                await NetService.Instance.Close(session.ConnectionHandle);
            }
        }

        public void AddListener<TResponse>(Action<TResponse> callback) where TResponse : IMessage => DoAddListener(typeof(TResponse), callback);

        public void AddListener(Type responseType, Action<IMessage> callback) => DoAddListener(responseType, callback);

        private void DoAddListener(Type responseType, Delegate callback) {
            Assert.IsNotNull(responseType);
            Assert.IsTrue(typeof(IMessage).IsAssignableFrom(responseType));
            Assert.IsNotNull(callback);

            var messageID = DFR_NetUtility.GetPacketID(responseType);
            void responseDelegate(INetPacket response) {
                if (DFR_NetUtility.TryGetMessage(responseType, response, out var result)) {
                    callback?.DynamicInvoke(result);
                }
            }
            m_CallbackDictionary.Add(callback, responseDelegate);
            var callbackInstanceID = new CallbackInstanceID(responseType, callback);
            var callbackID = new NetCallbackID { GUID = Guid.NewGuid() };
            m_CallbackInstanceIDDictionary.Add(callbackInstanceID, callbackID);
            var netMessageID = new NetPacketID<uint>(messageID.Value);
            if (m_Receiver == null) {
                m_AddingListenerQueue.Enqueue(new AddingListenerRecord {
                    MessageID = netMessageID,
                    CallbackID = callbackID,
                    Callback = responseDelegate,
                });
            } else {
                m_Receiver.AddListener(netMessageID, callbackID, responseDelegate);
            }
        }

        public void RemoveListener<TResponse>(Action<TResponse> callback) where TResponse : IMessage => DoRemoveListener(typeof(TResponse), callback);

        public void RemoveListener(Type responseType, Action<IMessage> callback) => DoRemoveListener(responseType, callback);

        private void DoRemoveListener(Type responseType, Delegate callback) {
            Assert.IsNotNull(responseType);
            Assert.IsTrue(typeof(IMessage).IsAssignableFrom((responseType)));
            Assert.IsNotNull(callback);

            var messageID = DFR_NetUtility.GetPacketID(responseType);
            if (m_CallbackDictionary.TryGetValue(callback, out var responseDelegate)) {
                m_CallbackDictionary.Remove(callback);
                var callbackInstanceID = new CallbackInstanceID(responseType, callback);
                var callbackID = m_CallbackInstanceIDDictionary[callbackInstanceID];
                m_Receiver.RemoveListener(new NetPacketID<uint>(messageID.Value), callbackID);
            }
        }
    }
}

namespace Internal.Runtime.Net {

    public interface IDFR_NetInternal {

        void SetSession(DFR_NetSession value);

        void SetHeartBeatTimeInterval(float value);

        void SetDefaultResponseTimeout(float value);

        void Initialize();

        void OnApplicationQuit();
    }
}